package net.stock.service;

import com.google.appengine.api.xmpp.JID;
import com.google.appengine.api.xmpp.MessageBuilder;
import com.google.appengine.api.xmpp.SendResponse;
import com.google.appengine.api.xmpp.XMPPService;
import net.stock.db.StockItem;
import net.stock.domain.ItemPrice;
import net.stock.domain.PriceChange;
import net.stock.utils.StockUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author naim
 * @date Dec 23, 2009
 */
@Service
public class StockService {
    private Logger log = Logger.getLogger(getClass().getName());

    private static String dateAndTime;
    private static ConcurrentHashMap<String, Float> latestPriceMap = new ConcurrentHashMap<String, Float>();
    private static ConcurrentHashMap<String, PriceChange> changeMap = new ConcurrentHashMap<String, PriceChange>();

    @Autowired
    private XMPPService xmppService;

    public String getDateAndTime() {
        return dateAndTime;
    }

    public Float getPrice(String key) {
        if (latestPriceMap.containsKey(key)) {
            return latestPriceMap.get(key);
        }
        return null;
    }

    public boolean isPriceMapInitialized() {
        if (latestPriceMap == null || latestPriceMap.size() == 0) {
            return downloadLatestPrice();
        } else {
            return true;
        }
    }

    public Collection<String> getCompanyNames() {
        if (!isPriceMapInitialized()) {
            throw new RuntimeException("Price map is not initialized");
        }
        return new HashSet<String>(latestPriceMap.keySet());
    }

    public boolean downloadLatestPrice() {
        try {
            Map<String, Float> map = new LinkedHashMap<String, Float>();
            URL url = new URL(StockUtils.DSEBD_TXT_URL);
            URLConnection connection = url.openConnection();
            
            connection.setConnectTimeout(StockUtils.CONNECT_TIMEOUT);
            connection.setReadTimeout(StockUtils.READ_TIMEOUT);
            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));

            String firstLine = reader.readLine();

            String line;
            while ((line = reader.readLine()) != null) {
                String[] parts = line.split("[\\s]+");
                if (parts.length == 2) {
                    String item = parts[0].trim();
                    String price = parts[1].trim();
                    Float val = StockUtils.parseFloat(price);
                    if (val != null && val.intValue() != 0) {
                        map.put(item, val);                        
                    }
                }
            }
            reader.close();

            if (map.size() > 0) {
                synchronized (this) {
                    latestPriceMap.putAll(map);
                    dateAndTime = firstLine;
                }
                log.log(Level.INFO, "Latest Price has been downloaded and saved, items.size = " + latestPriceMap.size());
                return true;
            }

        } catch (MalformedURLException e) {
            log.log(Level.SEVERE, "Could not download the latest price", e);
        } catch (IOException e) {
            log.log(Level.SEVERE, "Could not download the latest price", e);
        }
        return false;
    }

    public Map<String, Float> getLatestPriceMap() {
        if (!isPriceMapInitialized()) {
            throw new RuntimeException("Price map is not initialized");
        }
        return Collections.unmodifiableMap(latestPriceMap);
    }

    public List<ItemPrice> getItemPrices(Collection<StockItem> items) {
        Map<String, Float> latestPriceMap = getLatestPriceMap();

        List<ItemPrice> itemPriceList = new ArrayList<ItemPrice>();
        for (StockItem item : items) {
            String itemName = item.getName();

            if (latestPriceMap.containsKey(itemName)) {
                Float latestPrice = latestPriceMap.get(itemName);

                ItemPrice ip = new ItemPrice();
                ip.setItem(itemName);
                ip.setPrice(latestPrice);
                ip.setMyPrice(item.getMyPrice());

                if (item.getLow() != null && latestPrice <= item.getLow()) {
                    ip.setLow(true);
                }
                if (item.getHigh() != null && latestPrice >= item.getHigh()) {
                    ip.setHigh(true);
                }
                itemPriceList.add(ip);
            } else {
                log.log(Level.INFO, itemName + " is not found in latestPrice map.");
            }
        }
        Collections.sort(itemPriceList);
        return itemPriceList;
    }

    public String getPriceOnJson(Collection<StockItem> items) {
        List<ItemPrice> itemPriceList = getItemPrices(items);
        String dateAndTime = getDateAndTime();

        StringBuilder sb = new StringBuilder("{");
        if (itemPriceList.size() > 0) {
            sb.append("\"dt\":").append("\"").append(escapeForJson(dateAndTime)).append("\"");
            sb.append(",");

            int count = 0;
            sb.append("\"items\":").append("[");
            for (ItemPrice ip : itemPriceList) {
                if (count > 0) {
                    sb.append(",");
                }
                sb.append("{");
                sb.append("\"name\":").append("\"").append(escapeForJson(ip.getItem())).append("\"").append(",");
                sb.append("\"price\":").append("\"").append(ip.getPrice()).append("\"").append(",");
                if (ip.getMyPrice() != null) {
                    sb.append("\"myPrice\":").append("\"").append(ip.getMyPrice()).append("\"").append(",");
                }
                sb.append("\"low\":").append("\"").append(ip.isLow()).append("\"").append(",");
                sb.append("\"high\":").append("\"").append(ip.isHigh()).append("\"");
                sb.append("}");
                count++;
            }
            sb.append("]");
        }
        sb.append("}");

        return sb.toString();
    }

    public void sendNoifications(String email, List<ItemPrice> items) {
        if (!StockUtils.notEmpty(email)) {
            log.log(Level.INFO, "email is empty, can not send notifications.");
            return;
        }
        if (items == null || items.size() == 0) {
            log.log(Level.INFO, "items list is empty, not sending any notifications.");
            return;
        }

        StringBuilder lows = new StringBuilder();
        StringBuilder highs = new StringBuilder();
        StringBuilder other = new StringBuilder();
        for (ItemPrice ip : items) {
            if (ip.isLow() || ip.isHigh()) {
                if (ip.isLow()) {
                    if (lows.length() > 0) {
                        lows.append(", ");
                    }
                    lows.append(ip.getItem()).append(" (").append(ip.getPrice()).append(")");
                }
                if (ip.isHigh()) {
                    if (highs.length() > 0) {
                        highs.append(", ");
                    }
                    highs.append(ip.getItem()).append(" (").append(ip.getPrice()).append(")");
                }
            } else {
                if (other.length() > 0) {
                    other.append(", ");
                }
                other.append(ip.getItem()).append(" (").append(ip.getPrice()).append(")");
            }
        }

        StringBuilder messageBody = new StringBuilder("\n Significantly changed items:")
                .append("\n Lows: ").append(lows)
                .append("\n Highs: ").append(highs)
                .append("\n Others: ").append(other);


        log.log(Level.INFO, "Sending notification to user: " + email + "\t message: " + messageBody);
        
        boolean messageSent = sendXMPPMessage(new JID(email), messageBody.toString());
        if (!messageSent) {
            log.log(Level.INFO, "Cound not Send notification to: " + email);
            // TODO Send email
        }
    }

    public void processMessage(HttpServletRequest request) {
        try {
            com.google.appengine.api.xmpp.Message message = xmppService.parseMessage(request);
            String body = message.getBody();
            log.log(Level.INFO, "XMMP Message body = " + body);
            String[] items = body.split("[,\\s]");

            StringBuilder sb = new StringBuilder();
            for (String item : items) {
                if (!"".equals(item)) {
                    String key = item.trim().toUpperCase();
                    Float val = getPrice(key);
                    if (val != null) {
                        sb.append(key).append(": ").append(val).append("\n");
                    } else {
                        sb.append("No matching company found for: ").append(key);
                    }
                }
            }

            boolean success = sendXMPPMessage(message.getFromJid(), sb.toString());

            log.log(Level.INFO,
                    "Reply to " + message.getFromJid() + " was " 
                    + (success ? "successfull" : "unsuccessful"));

        } catch (IOException e) {
            log.log(Level.SEVERE, "Error in parsing Chat message from Request", e);
        }
    }

    public boolean sendXMPPMessage(JID jid, String msg) {
        com.google.appengine.api.xmpp.Message message = new MessageBuilder()
                .withRecipientJids(jid)
                .withBody(msg)
                .build();

        boolean messageSent = false;
        if (xmppService.getPresence(jid).isAvailable()) {
            SendResponse status = xmppService.sendMessage(message);
            log.log(Level.INFO, "XMPP SendResponse Status: " + status);
            messageSent = (status.getStatusMap().get(jid) == SendResponse.Status.SUCCESS);
        }

        return messageSent;
    }

    public void sendMail(String[] recipients, String subject, String body) {
        if (recipients == null || recipients.length == 0) {
            return;
        }

        Properties props = new Properties();
        Session session = Session.getDefaultInstance(props, null);

        try {
            javax.mail.Message msg = new MimeMessage(session);
            msg.setFrom(new InternetAddress(StockUtils.ADMIN_EMAIL));

            StringBuilder sb = new StringBuilder();
            Address[] addresses = new InternetAddress[recipients.length];
            for (int i = 0; i < recipients.length; i++) {
                addresses[i] = new InternetAddress(recipients[i]);
                if (i > 0) sb.append(", ");
                sb.append(recipients[i]);
            }
            msg.addRecipients(javax.mail.Message.RecipientType.TO, addresses);
            msg.setSubject(subject);
            msg.setText(body);


            log.log(Level.INFO, "Sending email to: " + sb.toString());
            Transport.send(msg);

        } catch (AddressException e) {
            log.log(Level.INFO, "Can not send email.", e);
        } catch (MessagingException e) {
            log.log(Level.INFO, "Can not send email.", e);
        }
    }

    private String escapeForJson(String str) {
        return str.replaceAll("[,\\{\\[]", " ");
    }
}
